using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.Type;
using NHibernate.Util;

namespace TelerikMvcGridCustomBindingHelper.NHibernate.Projections
{
    public abstract class OperatorProjection : SimpleProjection
    {
        private readonly IProjection[] _args;
        private readonly IType _returnType;

        private string _op;
        private string Op
        {
            get { return _op; }
            set
            {
                var trimmed = value.Trim();
                if (Array.IndexOf(AllowedOperators, trimmed) == -1)
                    throw new ArgumentOutOfRangeException("value", trimmed, "Not allowed operator");
                _op = " " + trimmed + " ";
            }
        }

        protected abstract string[] AllowedOperators { get; }

        protected OperatorProjection(string op, IType returnType, params IProjection[] args)
        {
            if (args.Length != 0 && args.Length < 2)
                throw new ArgumentOutOfRangeException("args", args.Length, "Requires at least 2 projections");

            Op = op;
            _returnType = returnType ?? NHibernateUtil.Decimal;
            _args = args;
        }

        public override SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            var sb = new SqlStringBuilder();
            sb.Add("(");

            for (var i = 0; i < _args.Length; i++)
            {
                var loc = (position + 1) * 1000 + i;
                var projectArg = GetProjectionArgument(criteriaQuery, criteria, _args[i], loc, enabledFilters);
                sb.Add(projectArg);

                if (i < _args.Length - 1)
                    sb.Add(Op);
            }
            sb.Add(")");
            sb.Add(" as ");
            sb.Add(GetColumnAliases(position)[0]);
            return sb.ToSqlString();
        }

        private static SqlString GetProjectionArgument(ICriteriaQuery criteriaQuery, ICriteria criteria, IProjection projection, int loc, IDictionary<string, IFilter> enabledFilters)
        {
            var sql = projection.ToSqlString(criteria, loc, criteriaQuery, enabledFilters);
            return SqlStringHelper.RemoveAsAliasesFromSql(sql);
        }

        public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            return new[] { _returnType };
        }

        public override bool IsAggregate
        {
            get { return false; }
        }

        public override bool IsGrouped
        {
            get { return _args.Any(projection => projection.IsGrouped); }
        }

        public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            var buf = new SqlStringBuilder();
            foreach (var projection in _args.Where(projection => projection.IsGrouped))
            {
                buf.Add(projection.ToGroupSqlString(criteria, criteriaQuery, enabledFilters)).Add(", ");
            }

            if (buf.Count >= 2)
                buf.RemoveAt(buf.Count - 1);

            return buf.ToSqlString();
        }

        public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            var types = new List<TypedValue>();
            foreach (var argTypes in _args.Select(projection => projection.GetTypedValues(criteria, criteriaQuery)))
            {
                types.AddRange(argTypes);
            }
            return types.ToArray();
        }
    }
}